/*
 * Created on 2005-2-22
 *
 */
package bma.common.langutil.core;

import java.io.File;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;
import java.net.URL;
import java.net.URLDecoder;
import java.util.LinkedList;
import java.util.List;

/**
 * 类（Class）工具类
 * 
 * @author 关中
 * @since 1.0 (imported from BMA.ESP Project)
 */
@SuppressWarnings("rawtypes")
public class ClassUtil {

	public static Class[][] PRIMARY = { { Boolean.class, Boolean.TYPE },
			{ Byte.class, Byte.TYPE }, { Character.class, Character.TYPE },
			{ Short.class, Short.TYPE }, { Integer.class, Integer.TYPE },
			{ Long.class, Long.TYPE }, { Float.class, Float.TYPE },
			{ Double.class, Double.TYPE }, { Void.class, Void.TYPE } };

	public static Class forName(String name) throws ClassNotFoundException {
		return getClassLoader().loadClass(name);
	}

	public static ClassLoader getClassLoader() {
		return Thread.currentThread().getContextClassLoader();
	}

	public static Class toMainClass(Class cls) {
		if (cls != null && cls.isPrimitive()) {
			for (Class[] pc : PRIMARY) {
				if (pc[1] == cls)
					return pc[0];
			}
		}
		return cls;
	}

	public static Class toPrimaryClass(Class cls) {
		if (cls == null)
			return cls;
		if (cls.isPrimitive())
			return cls;
		for (Class[] pc : PRIMARY) {
			if (pc[0] == cls)
				return pc[1];
		}
		return cls;
	}

	public static boolean isMainClass(Class cls) {
		for (Class[] pc : PRIMARY) {
			if (pc[1] == cls || pc[0] == cls)
				return true;
		}
		if (cls == String.class) {
			return true;
		}

		return false;
	}

	public static boolean isAssignFrom(Class[] cls, Class[] from) {
		if (cls == null || cls.length == 0) {
			if (from == null || from.length == 0) {
				return true;
			}
		}

		if (cls.length != from.length)
			return false;

		for (int i = 0; i < cls.length; i++) {
			if (!isAssignFrom(cls[i], from[i]))
				return false;
		}
		return true;
	}

	public static boolean isAssignFrom(Class<?> var, Class<?> value) {

		var = toMainClass(var);
		value = toMainClass(value);
		if (var.isAssignableFrom(value))
			return true;

		return false;
	}

	public static boolean isImplementInterface(Class<?> cls,
			Class<?> interfaceClass) {
		if (cls == null || interfaceClass == null)
			return false;
		return interfaceClass.isAssignableFrom(cls);
	}

	public static boolean isExtendFrom(Class<?> cls, Class<?> from) {
		if (cls == null || from == null)
			return false;
		return from.isAssignableFrom(cls);
	}

	public static Method lookupMethod(Class cls, String name, Class returnType,
			Class[] valcls) {
		return lookupMethod(cls, name, returnType, valcls, false);
	}

	public static Method lookupMethod(Class cls, String name, Class returnType,
			Class[] valcls, boolean all) {
		Method[] ms = cls.getMethods();
		if (ms != null && ms.length > 0) {
			for (int i = 0; i < ms.length; i++) {
				if (ms[i].getName().equals(name)) {
					if (valcls != null) {
						Class[] pcs = ms[i].getParameterTypes();
						if (!isAssignFrom(pcs, valcls)) {
							continue;
						}
					}
					if (returnType != null) {
						Class rt = ms[i].getReturnType();
						if (!isAssignFrom(rt, returnType)) {
							continue;
						}
					}
					return ms[i];
				}
			}
		}

		if (all) {
			Class sup = cls.getSuperclass();
			if (sup != null) {
				return lookupMethod(sup, name, returnType, valcls, all);
			}
		}

		return null;
	}

	public static Field getLocalField(Class cls, String name, Class type) {
		Field[] fs = cls.getDeclaredFields();
		for (Field f : fs) {
			if (f.getName().equals(name)) {
				if (type == null || isExtendFrom(f.getType(), type)) {
					return f;
				}
			}
		}
		return null;
	}

	public static Field getField(Class cls, String name, Class type) {
		Field f = getLocalField(cls, name, type);
		if (f == null) {
			Class s = cls.getSuperclass();
			if (s != null)
				return getField(s, name, type);
		}
		return f;
	}

	public static Method getMethod(Class<?> cls, String name, Class[] valcls)
			throws Exception {
		Method m = lookupMethod(cls, name, null, valcls);
		if (m != null)
			return m;
		// let's throw NoSuchMethodException
		return cls.getMethod(name, valcls);
	}

	public static String getShortName(String className) {
		Preconditions.checkArgument(ValueUtil.notEmpty(className),
				"The class name must not be empty");

		char[] chars = className.toCharArray();
		int lastDot = 0;
		for (int i = 0; i < chars.length; i++) {
			if (chars[i] == '.') {
				lastDot = i + 1;
			} else if (chars[i] == '$') { // handle
				// inner classes
				chars[i] = '.';
			}
		}
		return new String(chars, lastDot, chars.length - lastDot);
	}

	public static String getShortName(Class cls) {
		if (cls == null)
			return null;
		return getShortName(cls.getName());
	}

	public static Class toClass(Type type) {
		if (type instanceof Class) {
			return (Class) type;
		}
		if (type instanceof ParameterizedType) {
			return toClass(((ParameterizedType) type).getRawType());
		}
		return null;
	}

	public static String getResourceName(Class cls) {
		return getResourceName(cls, null);
	}

	public static String getResourceName(Class cls, String ext) {
		String name = cls.getName();
		name = name.replace('.', '/');
		if (ext != null)
			name += ext;
		return name;
	}

	public static String getClassPackageResourceName(Class clsPackage,
			String resourceName) {
		String name = clsPackage.getPackage().getName();
		name = name.replace('.', '/');
		return name + "/" + resourceName;
	}

	public static String[] listPackageClass(String packageName)
			throws Exception {

		String name = new String(packageName);
		if (!name.startsWith("/")) {
			// name = "/" + name;
		}

		name = name.replace('.', '/');

		// Get a File object for the package
		URL url = Thread.currentThread().getContextClassLoader()
				.getResource(name);
		String path = URLDecoder.decode(url.getFile(), "UTF-8");
		File directory = new File(path);

		if (!directory.exists()) {
			return new String[0];
		}

		// Get the list of the files contained in the package
		List<String> r = new LinkedList<String>();
		String[] files = directory.list();
		for (String file : files) {
			// we are only interested in .class files
			if (file.endsWith(".class")) {
				// removes the .class extension
				String classname = file.substring(0,
						file.length() - ".class".length());
				r.add(packageName + "." + classname);
			}
		}
		return r.toArray(new String[0]);

	}

	public static URL getResource(String res) {
		return getClassLoader().getResource(res);
	}

	public static boolean isAbstract(Class cls) {
		if (cls.isInterface())
			return true;
		if (Modifier.isAbstract(cls.getModifiers()))
			return true;
		if (cls.isEnum())
			return true;
		if (cls.isAnnotation())
			return true;
		return false;
	}

	public static Constructor getConstructor(Class cls, Class[] valcls) {

		Constructor[] cs = cls.getConstructors();
		for (Constructor c : cs) {
			Class[] pcs = c.getParameterTypes();
			if (isAssignFrom(pcs, valcls)) {
				return c;
			}
		}
		return null;
	}

	public static <TYPE> TYPE cast(Class<TYPE> cls, Object obj) {
		if (obj == null)
			return null;
		if (cls.isInstance(obj))
			return cls.cast(obj);
		// if (obj instanceof ClassCast) {
		// ClassCast oc = (ClassCast) obj;
		// return oc.castInstance(cls);
		// }
		return null;
	}

	// public static <TYPE> TYPE cast(Class<TYPE> cls, ClassCast cc, Object obj)
	// {
	// if (cc != null) {
	// if (cls.isInstance(cc))
	// return cls.cast(cc);
	// }
	// return cast(cls, obj);
	// }

	public static URL getClassLocation(Class cls) {
		return cls.getProtectionDomain().getCodeSource().getLocation();
	}

	public static Class getClass(Type type) {
		if (type instanceof Class) {
			return (Class) type;
		} else if (type instanceof GenericArrayType) {
			return Array.class;
		} else if (type instanceof ParameterizedType) {
			ParameterizedType pcls = (ParameterizedType) type;
			Type c = pcls.getRawType();
			if (c instanceof Class) {
				return (Class) c;
			}
			return getClass(c);
		} else if (type instanceof WildcardType) {
			WildcardType wc = (WildcardType) type;
			Type[] ts = wc.getUpperBounds();
			if (ts == null || ts.length == 0) {
				return Object.class;
			} else {
				if (ts.length > 1) {
					throw new UnsupportedOperationException("unknow type ["
							+ type + "]");
				}
				return getClass(ts[0]);
			}
		} else if (type instanceof TypeVariable) {
			TypeVariable tc = (TypeVariable) type;
			Type[] ts = tc.getBounds();
			if (ts == null || ts.length == 0) {
				return Object.class;
			} else {
				if (ts.length > 1) {
					throw new UnsupportedOperationException("unknow type ["
							+ type + "]");
				}
				return getClass(ts[0]);
			}
		} else {
			throw new UnsupportedOperationException("unknow type [" + type
					+ "]");
		}
	}

	public static Method getMethodByGeneric(Class cls,
			String methodGenericString, boolean searchSuper) {
		Method[] ms = cls.getMethods();
		if (ms != null && ms.length > 0) {
			for (int i = 0; i < ms.length; i++) {
				if (ms[i].toGenericString().equals(methodGenericString)) {
					return ms[i];
				}
			}
		}

		if (searchSuper) {
			Class sup = cls.getSuperclass();
			if (sup != null) {
				return getMethodByGeneric(sup, methodGenericString, searchSuper);
			}
		}

		return null;
	}

	public static boolean isArray(Class cls, Class componentType) {
		return cls.isArray() && cls.getComponentType().equals(componentType);
	}
}